home *** CD-ROM | disk | FTP | other *** search
/ Turnbull China Bikeride / Turnbull China Bikeride - Disc 2.iso / BARNET / UTILITIES / BASCAT / bascat / bascat.c next >
C/C++ Source or Header  |  1996-05-10  |  12KB  |  552 lines

  1. /*
  2.  * bascat.c
  3.  *
  4.  * Display BBC BASIC programs more or less anywhere
  5.  *
  6.  * (c) 1996 Matthew Wilcox and Mark Wooding
  7.  */
  8.  
  9. /*----- Licencing note ----------------------------------------------------*
  10.  *
  11.  * This program may be freely redistributed under the terms of the GNU
  12.  * Public Licence, either version 2, or at your discretion, any later
  13.  * version.
  14.  */
  15.  
  16. /*----- Header files ------------------------------------------------------*/
  17.  
  18. /* --- ANSI library headers --- */
  19.  
  20. #include <ctype.h>
  21. #include <errno.h>
  22. #include <signal.h>
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26.  
  27. /* --- Operating system specific headers --- */
  28.  
  29. #include <curses.h>
  30. #include <term.h>
  31. #include <unistd.h>
  32.  
  33. /* --- Private headers --- */
  34.  
  35. #include "mdwopt.h"
  36.  
  37. /*----- Version information -----------------------------------------------*/
  38.  
  39. #define version "1.11"
  40.  
  41. #ifdef DEBUG
  42. #define d(x) x
  43. #else
  44. #define d(x)
  45. #endif
  46.  
  47. /*----- Tokenisation tables -----------------------------------------------*
  48.  *
  49.  * These tables are ripped out of Matthew's original implementation.
  50.  */
  51.  
  52. static char *bcTok__base[] = { "OTHERWISE",
  53.    "AND","DIV","EOR","MOD","OR","ERROR","LINE","OFF",
  54.    "STEP","SPC","TAB(","ELSE","THEN","*","OPENIN","PTR",
  55.    "PAGE","TIME","LOMEM","HIMEM","ABS","ACS","ADVAL","ASC",
  56.    "ASN","ATN","BGET","COS","COUNT","DEG","ERL","ERR",
  57.    "EVAL","EXP","EXT","FALSE","FN","GET","INKEY","INSTR(",
  58.    "INT","LEN","LN","LOG","NOT","OPENUP","OPENOUT","PI",
  59.    "POINT(","POS","RAD","RND","SGN","SIN","SQR","TAN",
  60.    "TO","TRUE","USR","VAL","VPOS","CHR$","GET$","INKEY$",
  61.    "LEFT$(","MID$(","RIGHT$(","STR$","STRING$(","EOF","*","*",
  62.    "*","WHEN","OF","ENDCASE","ELSE","ENDIF","ENDWHILE","PTR",
  63.    "PAGE","TIME","LOMEM","HIMEM","SOUND","BPUT","CALL","CHAIN",
  64.    "CLEAR","CLOSE","CLG","CLS","DATA","DEF","DIM","DRAW",
  65.    "END","ENDPROC","ENVELOPE","FOR","GOSUB","GOTO","GCOL","IF",
  66.    "INPUT","LET","LOCAL","MODE","MOVE","NEXT","ON","VDU",
  67.    "PLOT","PRINT","PROC","READ","REM","REPEAT","REPORT","RESTORE",
  68.    "RETURN","RUN","STOP","COLOUR","TRACE","UNTIL","WIDTH","OSCLI"
  69. };
  70.  
  71. static char *bcTok__c6[] = { "SUM", "BEAT"
  72. };
  73.  
  74. static char *bcTok__c7[] = { "APPEND","AUTO",
  75.    "CRUNCH","DELETE","EDIT","HELP","LIST","LOAD","LVAR","NEW",
  76.    "OLD","RENUMBER","SAVE","TEXTLOAD","TEXTSAVE","TWIN","TWINO","INSTALL"
  77. };
  78.  
  79. static char *bcTok__c8[] = { "CASE","CIRCLE",
  80.    "FILL","ORIGIN","POINT","RECTANGLE","SWAP","WHILE","WAIT","MOUSE",
  81.    "QUIT","SYS","INSTALL","LIBRARY","TINT","ELLIPSE","BEATS","TEMPO",
  82.    "VOICES","VOICE","STEREO","OVERLAY"
  83. };
  84.  
  85. #define items(array) (sizeof(array) / sizeof((array)[0]))
  86.  
  87. /*----- Static variables --------------------------------------------------*/
  88.  
  89. enum {
  90.   s_keyword,                /* Expecting a keyword next */
  91.   s_normal,                /* Normal state, reading input */
  92.   s_comment,                /* In a command (or literal *cmd) */
  93.   s_quote,                /* Inside a quoted string */
  94.   s_c6, s_c7, s_c8,            /* Various shift states */
  95.   s_dummy
  96. };
  97.  
  98. static bc__state=s_normal;        /* Current detokenisation state */
  99.  
  100. enum {
  101.   f_highlight = 1,            /* Highlight keywords and things */
  102.   f_linenumbers = 2,            /* Display linenumbers on left */
  103.   f_tty = 4,                /* We're writing to TTY (or pager) */
  104.   f_less = 8,                /* We're piping through `less' */
  105.   f_dummy
  106. };
  107.  
  108. static int bc__flags;            /* Various options flags */
  109.  
  110. static char bc__termcap[2048];        /* Terminal capabilities buffer */
  111. static char *bc__pager=0;        /* Pointer to pager to use */
  112.  
  113. /*----- Main code ---------------------------------------------------------*/
  114.  
  115. /* --- die --- *
  116.  *
  117.  * Arguments:    char *p == pointer to a string
  118.  *
  119.  * Returns:    1
  120.  *
  121.  * Use:        Reports an error to the user and falls over flat on its face.
  122.  */
  123.  
  124. static int die(const char *p)
  125. {
  126.   fprintf(stderr,"%s: %s\n",optprog,p);
  127.   return (1);
  128. }
  129.  
  130. /* --- bc__keyword --- *
  131.  *
  132.  * Arguments:    char *s == pointer to keyword string
  133.  *        FILE *fp == stream to write onto
  134.  *
  135.  * Returns:    --
  136.  *
  137.  * Use:        Displays a keyword in a nice way.  There's some nasty hacking
  138.  *        here to make GNU's `less' work properly.  `more' appeats to
  139.  *        cope with highlighting codes OK, so that's fine.  `less'
  140.  *        prefers it if we attempt to `overstrike' the bolded
  141.  *        characters.  What fun...
  142.  */
  143.  
  144. static void bc__keyword(const char *s,FILE *fp)
  145. {
  146.   if ((~bc__flags & (f_less | f_highlight)) == 0)
  147.   {
  148.     while (*s)
  149.     {
  150.       putc(*s,fp);
  151.       putc(8,fp); /* evil... */
  152.       putc(*s,fp);
  153.       s++;
  154.     }
  155.   }
  156.   else
  157.   {
  158.     static char buff[24];
  159.     static char *hs,*he,*p=buff;
  160.  
  161.     if (!hs)
  162.     {
  163.       if (bc__flags & f_highlight)
  164.       {
  165.     hs=tgetstr("md",&p);
  166.     he=tgetstr("me",&p);
  167.       }
  168.       else
  169.     hs=he="";
  170.     }
  171.  
  172.     fputs(hs,fp);
  173.     fputs(s,fp);
  174.     fputs(he,fp);
  175.   }
  176. }
  177.  
  178. /* --- bc__mbtok --- *
  179.  *
  180.  * Arguments:    int byte == the current byte
  181.  *        char *t[] == pointer to token table
  182.  *        int n == number of items in token table
  183.  *        FILE *fp == stream to write onto
  184.  *
  185.  * Returns:    0 if everything's OK
  186.  *
  187.  * Use:        Decodes multibyte tokens.
  188.  */
  189.  
  190. static int bc__mbtok(int byte,char *t[],int n,FILE *fp)
  191. {
  192.   byte-=0x8E;
  193.   if (byte>=n)
  194.     return (die("Bad program: invalid multibyte token"));
  195.   bc__keyword(t[byte],fp);
  196.   bc__state=s_normal;
  197.   return(0);
  198. }
  199.  
  200. /* --- bc__decode --- *
  201.  *
  202.  * Arguments:    int byte == byte to decode
  203.  *        FILE *fp == stream to write onto
  204.  *
  205.  * Returns:    0 if everything's going OK
  206.  *
  207.  * Use:        Decodes a byte, changing states as necessary.
  208.  */
  209.  
  210. static int bc__decode(int byte,FILE *fp)
  211. {
  212.   switch (bc__state)
  213.   {
  214.     case s_keyword:
  215.       if (byte=='*')
  216.     bc__state=s_comment;
  217.       /* Fall through here */
  218.     case s_normal:
  219.       if (byte>=0x7F)
  220.       {
  221.     switch (byte)
  222.     {
  223.       case 0xC6:
  224.         bc__state=s_c6;
  225.         break;
  226.       case 0xC7:
  227.         bc__state=s_c7;
  228.         break;
  229.       case 0xC8:
  230.         bc__state=s_c8;
  231.         break;
  232.       case 0x8B: /* ELSE */
  233.       case 0x8C: /* THEN */
  234.       case 0xF5: /* REPEAT (a funny one) */
  235.         bc__state=s_keyword;
  236.         bc__keyword(bcTok__base[byte-0x7F],fp);
  237.         break;
  238.       case 0xDC: /* DATA */
  239.       case 0xF4: /* REM */
  240.         bc__state=s_comment;
  241.         /* Fall through here */
  242.       default:
  243.         bc__keyword(bcTok__base[byte-0x7F],fp);
  244.         break;
  245.     }
  246.       }
  247.       else
  248.       {
  249.     if (byte=='"')
  250.       bc__state=s_quote;
  251.     fputc(byte,fp);
  252.       }
  253.       break;
  254.     case s_quote:
  255.       if (byte=='"')
  256.     bc__state=s_normal;
  257.       /* Fall through here */
  258.     case s_comment:
  259.       fputc(byte,fp);
  260.       break;
  261.     case s_c6:
  262.       return (bc__mbtok(byte,bcTok__c6,items(bcTok__c6),fp));
  263.       break;
  264.     case s_c7:
  265.       return (bc__mbtok(byte,bcTok__c7,items(bcTok__c7),fp));
  266.       break;
  267.     case s_c8:
  268.       return (bc__mbtok(byte,bcTok__c8,items(bcTok__c8),fp));
  269.       break;
  270.   }
  271.   return(0);
  272. }
  273.  
  274. /* --- bc__line --- *
  275.  *
  276.  * Arguments:    FILE *in == input stream to read
  277.  *        FILE *out == output stream to write
  278.  *
  279.  * Returns:    Zero if there's another line after this one.
  280.  *
  281.  * Use:        Decodes a BASIC line into stuff to be written.
  282.  */
  283.  
  284. static int bc__line(FILE *in,FILE *out)
  285. {
  286.   /* --- Read the line number --- */
  287.  
  288.   {
  289.     int a,b;
  290.  
  291.     a=getc(in);  d( fprintf(stderr,"ln_0 == %i\n",a); )
  292.     if (a==EOF)
  293.       goto eof;
  294.     if (a==0xFF)
  295.       return (1);
  296.  
  297.     b=getc(in);  d( fprintf(stderr,"ln_1 == %i\n",b); )
  298.     if (b==EOF)
  299.       goto eof;
  300.  
  301.     if (bc__flags & f_linenumbers)
  302.       fprintf(out,"%5i",(a<<8)+b);
  303.   }
  304.  
  305.   {
  306.     int len;
  307.     int byte;
  308.  
  309.     len=getc(in);  d( fprintf(stderr,"linelen == %i\n",len); )
  310.     if (len==EOF)
  311.       goto eof;
  312.     len-=4;
  313.  
  314.     bc__state=s_normal;
  315.     while (len)
  316.     {
  317.       byte=getc(in);  d( fprintf(stderr,"byte == %i\n",byte); )
  318.       if (byte==EOF)
  319.     goto eof;
  320.       bc__decode(byte,out);
  321.       len--;
  322.     }
  323.     putc('\n',out);
  324.  
  325.     byte=getc(in);  d( fprintf(stderr,"eol == %i\n",byte); )
  326.     if (byte==EOF)
  327.       goto eof;
  328.     else if (byte!=0x0D)
  329.       return (die("Bad program: expected end-of-line delimiter"));
  330.   }
  331.  
  332.   return (0);
  333.  
  334. eof:
  335.   return (die("Bad program: unexpected end-of-file"));
  336. }
  337.  
  338. /* --- bc__file --- *
  339.  *
  340.  * Arguments:    FILE *in == the input stream
  341.  *        FILE *out == the output stream
  342.  *
  343.  * Returns:    --
  344.  *
  345.  * Use:        Decodes an entire file.
  346.  */
  347.  
  348. static void bc__file(FILE *in,FILE *out)
  349. {
  350.   int byte;
  351.  
  352.   /* --- Check for the inital newline char --- */
  353.  
  354.   byte=getc(in);
  355.   if (byte!=0x0D)
  356.     die("Bad program: doesn't start with a newline");
  357.  
  358.   /* --- Now read the lines in one by one --- */
  359.  
  360.   while (!bc__line(in,out))
  361.     ;
  362.  
  363.   /* --- Check that we're really at end-of-file --- */
  364.  
  365.   byte=getc(in);
  366.   if (byte!=EOF)
  367.     die("Found data after end of program");
  368. }
  369.  
  370. /* --- bc__sigPipe --- *
  371.  *
  372.  * Arguments:    int s == signal number
  373.  *
  374.  * Returns:    Doesn't
  375.  *
  376.  * Use:        Handles SIGPIPE signals, and gracefully kills the program.
  377.  */
  378.  
  379. static void bc__sigPipe(int s)
  380. {
  381.   exit(0); /* Gracefully, oh yes */
  382. }
  383.  
  384. /* --- bc__options --- *
  385.  *
  386.  * Arguments:    int c == number of arguments
  387.  *        char *v[] == pointer to arguments
  388.  *        char *s == pointer to short options
  389.  *        struct option *o == pointer to long options
  390.  *
  391.  * Returns:    --
  392.  *
  393.  * Use:        Parses lots of arguments.
  394.  */
  395.  
  396. static void bc__options(int c,char *v[],const char *s,const struct option *o)
  397. {
  398.   int i;
  399.   
  400.   for (;;)
  401.   {
  402.     i=mdwopt(c,v,s,o,0,0,gFlag_negation | gFlag_envVar);
  403.     if (i==-1)
  404.       break;
  405.  
  406.     switch (i)
  407.     {
  408.       case 'v':
  409.       case 'h':
  410.         printf("%s v. " version " (" __DATE__ ")\n",optprog);
  411.     if (i=='v')
  412.       exit(0);
  413.     printf("\n"
  414.            "%s [-hv] [-n|+n] [-l|+l] [-p <pager>] [file...]\n"
  415.            "\n"
  416.            "Types BBC BASIC programs in a readable way.  Options "
  417.            "currently supported are as\n"
  418.            "follows:\n"
  419.            "\n"
  420.            "-h, --help:            Displays this evil help message\n"
  421.            "-v, --version:         Displays the current version number\n"
  422.            "-n, --line-numbers:    Displays line numbers for each line\n"
  423.            "-l, --highlight:       Attempts to highlight keywords\n"
  424.            "-p, --pager=<pager>:   Sets pager to use (default $PAGER)\n"
  425.            "\n"
  426.            "Prefix long options with `no-' to cancel them.  Use `+' to "
  427.            "cancel short options.\n"
  428.            "Options can also be placed in the `BASCAT' environment "
  429.            "variable, if you don't\n"
  430.            "like the standard settings.\n",
  431.            optprog);
  432.     exit(0);
  433.     break;
  434.       case 'n':
  435.     bc__flags |= f_linenumbers;
  436.     break;
  437.       case 'n' | gFlag_negated:
  438.     bc__flags &= ~f_linenumbers;
  439.     break;
  440.       case 'l':
  441.     bc__flags |= f_highlight;
  442.     break;
  443.       case 'l' | gFlag_negated:
  444.     bc__flags &= ~f_highlight;
  445.     break;
  446.       case 'p':
  447.     bc__pager=optarg;
  448.     break;
  449.     }
  450.   }
  451. }
  452.  
  453. /* --- main --- *
  454.  *
  455.  * Arguments:    int argc == number of arguments
  456.  *        char *argc[] == pointer to what the arguments are
  457.  *
  458.  * Returns:    0 if it all worked
  459.  *
  460.  * Use:        Displays BASIC programs.
  461.  */
  462.  
  463. int main(int argc,char *argv[])
  464. {
  465.   static struct option opts[]={
  466.     {"help",0,0,'h'},
  467.     {"version",0,0,'v'},
  468.     {"line-numbers",gFlag_negate,0,'n'},
  469.     {"highlight",gFlag_negate,0,'l'},
  470.     {"pager",gFlag_argReq,0,'p'},
  471.     {0,0,0,0}
  472.   };
  473.   static char *shortopts="hvn+l+p:";
  474.  
  475.   /* --- Parse the command line options --- */
  476.  
  477.   bc__options(argc,argv,shortopts,opts);
  478.  
  479.   /* --- Now do the job --- */
  480.  
  481.   if (optind==argc && isatty(0))
  482.   {
  483.     fprintf(stderr,
  484.         "%s: No filenames given, and standard input is a tty\n"
  485.         "To force reading from stdin, use `%s -'.  For help, type "
  486.         "`%s --help'.\n",
  487.         optprog,optprog,optprog);
  488.     exit(0);
  489.   }
  490.  
  491.   if (bc__flags & f_highlight)
  492.     tgetent(bc__termcap,getenv("TERM"));
  493.  
  494.   {
  495.     FILE *in;
  496.     FILE *out;
  497.  
  498.     /* --- If output is to a terminal, try paging --- *
  499.      *
  500.      * All programs which spew text should do this ;-)
  501.      */
  502.  
  503.     if (isatty(1))
  504.     {
  505.       if (!bc__pager)
  506.     bc__pager=getenv("PAGER");
  507.       if (!bc__pager)
  508.     bc__pager="more"; /* Worth a try */
  509.       if (strstr(bc__pager,"less"))
  510.     bc__flags |= f_less; /* HACK!!! */
  511.       out=popen(bc__pager,"w");
  512.       if (!out)
  513.     out=stdout;
  514.       else
  515.       {
  516.     bc__flags |= f_tty;
  517.     signal(SIGPIPE,bc__sigPipe);
  518.       }
  519.     }
  520.     else
  521.       out=stdout;
  522.  
  523.     /* --- Now go through all the files --- */
  524.  
  525.     if (optind==argc)
  526.       bc__file(stdin,out);
  527.     else while (optind<argc)
  528.     {
  529.       if (strcmp(argv[optind],"-")==0)
  530.         bc__file(stdin,out);
  531.       else
  532.       {
  533.     in=fopen(argv[optind],"rb");
  534.     if (!in)
  535.       fprintf(stderr,
  536.           "%s: Couldn't open input file: %s\n",
  537.           optprog,strerror(errno));
  538.     else
  539.     {
  540.       bc__file(in,out);
  541.       fclose(in);
  542.     }
  543.       }
  544.       optind++;
  545.     }
  546.     if (bc__flags & f_tty)
  547.       pclose(out);
  548.   }
  549.  
  550.   return (0);
  551. }
  552.